﻿//Chart.xaml.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Xaml
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Drawing;
	using System.Linq;
	using System.Windows.Forms.DataVisualization.Charting;
	using System.Windows.Media;
	using WpfColor = System.Windows.Media.Color;
	using WinColor = System.Drawing.Color;
	using WinChartArea = System.Windows.Forms.DataVisualization.Charting.ChartArea;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Serialization;
	using Ecng.Xaml;

	using StockSharp.BusinessEntities;
	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Indicators;

	/// <summary>
	/// Графический компонент отображения свечных графиков.
	/// </summary>
	public partial class Chart : IChart
	{
		private sealed class AreaInfo
		{
			private readonly ChartArea _area;
			private readonly Chart _chart;
			private readonly Dictionary<ChartIndicatorElement, Tuple<IDictionary<IIndicator, Series>, Legend>> _indicatorSeries = new Dictionary<ChartIndicatorElement, Tuple<IDictionary<IIndicator, Series>, Legend>>();
			private readonly Dictionary<ChartCandleElement, RefPair<Series, DataPoint>> _candlesSeries = new Dictionary<ChartCandleElement, RefPair<Series, DataPoint>>();
			private readonly Dictionary<ChartTradeElement, Tuple<Series, Series>> _tradeSeries = new Dictionary<ChartTradeElement, Tuple<Series, Series>>();
			private readonly Dictionary<ChartOrderElement, Tuple<Series, Series>> _orderSeries = new Dictionary<ChartOrderElement, Tuple<Series, Series>>();

			private readonly CachedSynchronizedSet<IChartElement> _elements = new CachedSynchronizedSet<IChartElement>(); 

			private double? _minY;
			private double? _maxY;

			public AreaInfo(ChartArea area, WinChartArea nativeArea, Chart chart)
			{
				if (area == null)
					throw new ArgumentNullException("area");

				if (nativeArea == null)
					throw new ArgumentNullException("nativeArea");

				if (chart == null)
					throw new ArgumentNullException("chart");

				area.Elements.Added += AddSeries;
				area.Elements.Removing += RemoveSeries;
				area.Elements.Clearing += () => area.Elements.ForEach(RemoveSeries);

				_area = area;
				NativeArea = nativeArea;
				_chart = chart;

				area.Elements.ForEach(AddSeries);
			}

			public readonly WinChartArea NativeArea;

			private void AddSeries(IDictionary<IIndicator, Series> seriesDict, IIndicator indicator, ChartIndicatorElement elem)
			{
				if (elem.IndicatorPainter != null)
				{
					var dict = elem.IndicatorPainter.Init(NativeArea.Name, _chart.IsIndexed);

					foreach (var aSeries in dict)
					{
						seriesDict.Add(aSeries);
						_chart.MsChart.AddSeries(NativeArea, aSeries.Value);
					}
				}
				else
				{
					var complexIndicator = indicator as IComplexIndicator;

					if (complexIndicator != null)
					{
						foreach (var innerIndicator in complexIndicator.InnerIndicators)
						{
							AddSeries(seriesDict, innerIndicator, elem);
						}
					}
					else
					{
						var series = new Series("Indicator".SeriesName())
						{
							ChartArea = NativeArea.Name,
							ChartType = SeriesChartType.Line,
							XValueType = ChartValueType.DateTime,
							IsXValueIndexed = _chart.IsIndexed,
							BorderWidth = 2,
							//Color = Color.Green,
							//Color = item.Color.ToWin(), // падало из-за этого
							//ToolTip = item.ToolTip // но скорее из-за вот этого
							LabelFormat = "{0:#,##0.00}",
						};

						seriesDict.Add(indicator, series);
						_chart.MsChart.AddSeries(NativeArea, series);
					}
				}
			}

			private void AddSeries(IChartElement elem)
			{
				_elements.Add(elem);

				var indElem = elem as ChartIndicatorElement;

				if (indElem != null)
				{
					var seriesDict = new Dictionary<IIndicator, Series>();
					AddSeries(seriesDict, indElem.Indicator, indElem);

					Legend legend = null;

					if (indElem.IsLegend)
					{
						legend = new Legend("Legeng".SeriesName())
						{
							BackColor = WinColor.FromArgb(64, 165, 191, 228),
							BackSecondaryColor = System.Drawing.Color.White,
							BackGradientStyle = GradientStyle.TopBottom,
							BorderColor = WinColor.FromArgb(64, 64, 64, 64),
							BorderDashStyle = ChartDashStyle.Solid,
							ShadowColor = WinColor.Transparent,
							DockedToChartArea = NativeArea.Name
							//Docking = Docking.Bottom,
							//TableStyle = LegendTableStyle.Wide,
							//TitleAlignment = StringAlignment.Near,
						};

						_chart.MsChart.Legends.Add(legend);

						seriesDict.Values.First().LegendText = indElem.Title;
					}

					_indicatorSeries.Add(indElem, new Tuple<IDictionary<IIndicator, Series>, Legend>(seriesDict, legend));

					return;
				}

				var candleElem = elem as ChartCandleElement;

				if (candleElem != null)
				{
					var series = new Series("Candles".SeriesName())
					{
						ChartArea = NativeArea.Name,
						ChartType = SeriesChartType.Candlestick,
						XValueType = ChartValueType.DateTime,
						IsXValueIndexed = _chart.IsIndexed,
						//ToolTip = "#VALX{yyyy-MM-dd HH:mm:ss}\n\nHigh: #VALY2\nLow: #VALY\nOpen: #VALY3\nClose: #VALY4"
					};

					_candlesSeries.Add(candleElem, new RefPair<Series, DataPoint>(series, null));
					_chart.MsChart.AddSeries(NativeArea, series);
					return;
				}

				var tradeElem = elem as ChartTradeElement;

				if (tradeElem != null)
				{
					var buySeries = new Series("TradesBuy".SeriesName())
					{
						ChartArea = NativeArea.Name,
						ChartType = SeriesChartType.Point,
						XValueType = ChartValueType.DateTime,
						IsXValueIndexed = false,//_chart.IsIndexed,
						BorderWidth = 2,
						MarkerStyle = MarkerStyle.Circle,
						MarkerSize = 5,
						MarkerColor = tradeElem.BuyColor.ToWin(),
						LabelFormat = "{0:#,##0.00}"
					};

					var sellSeries = new Series("TradesSell".SeriesName())
					{
						ChartArea = NativeArea.Name,
						ChartType = SeriesChartType.Point,
						XValueType = ChartValueType.DateTime,
						IsXValueIndexed = false,//_chart.IsIndexed,
						BorderWidth = 2,
						MarkerStyle = MarkerStyle.Circle,
						MarkerSize = 5,
						MarkerColor = tradeElem.SellColor.ToWin(),
						LabelFormat = "{0:#,##0.00}"
					};

					_tradeSeries.Add(tradeElem, new Tuple<Series, Series>(buySeries, sellSeries));
					_chart.MsChart.AddSeries(NativeArea, buySeries);
					_chart.MsChart.AddSeries(NativeArea, sellSeries);
					return;
				}

				var orderElem = elem as ChartOrderElement;

				if (orderElem != null)
				{
					var buySeries = new Series("OrdersBuy".SeriesName())
					{
						ChartArea = NativeArea.Name,
						ChartType = SeriesChartType.Point,
						XValueType = ChartValueType.DateTime,
						IsXValueIndexed = false,//_chart.IsIndexed,
						BorderWidth = 5,
						MarkerStyle = MarkerStyle.Diamond,
						MarkerSize = 10,
						MarkerColor = WinColor.White,
						MarkerBorderColor = orderElem.BuyColor.ToWin(),
						LabelFormat = "{0:#,##0.00}"
					};

					var sellSeries = new Series("OrdersSell".SeriesName())
					{
						ChartArea = NativeArea.Name,
						ChartType = SeriesChartType.Point,
						XValueType = ChartValueType.DateTime,
						IsXValueIndexed = false,//_chart.IsIndexed,
						BorderWidth = 5,
						MarkerStyle = MarkerStyle.Diamond,
						MarkerSize = 10,
						MarkerColor = WinColor.White,
						MarkerBorderColor = orderElem.SellColor.ToWin(),
						LabelFormat = "{0:#,##0.00}"
					};

					_orderSeries.Add(orderElem, new Tuple<Series, Series>(buySeries, sellSeries));
					_chart.MsChart.AddSeries(NativeArea, buySeries);
					_chart.MsChart.AddSeries(NativeArea, sellSeries);
					return;
				}

				throw new ArgumentException("Неизвестный элемент графика {0}.".Put(elem));
			}

			private void RemoveSeries(IChartElement elem)
			{
				try
				{
					var indElem = elem as ChartIndicatorElement;

					if (indElem != null)
					{
						var tuple = _indicatorSeries[indElem];

						tuple.Item1.Values.ForEach(s => _chart.MsChart.RemoveSeries(NativeArea, s));
						_indicatorSeries.Remove(indElem);

						if (indElem.IsLegend)
						{
							_chart.MsChart.Legends.Remove(tuple.Item2);
						}

						return;
					}

					var candleElem = elem as ChartCandleElement;

					if (candleElem != null)
					{
						_chart.MsChart.RemoveSeries(NativeArea, _candlesSeries[candleElem].First);
						_candlesSeries.Remove(candleElem);
						return;
					}

					var tradeElem = elem as ChartTradeElement;

					if (tradeElem != null)
					{
						_chart.MsChart.RemoveSeries(NativeArea, _tradeSeries[tradeElem].Item1);
						_chart.MsChart.RemoveSeries(NativeArea, _tradeSeries[tradeElem].Item2);
						_tradeSeries.Remove(tradeElem);
						return;
					}

					var orderElem = elem as ChartOrderElement;

					if (orderElem != null)
					{
						_chart.MsChart.RemoveSeries(NativeArea, _orderSeries[orderElem].Item1);
						_chart.MsChart.RemoveSeries(NativeArea, _orderSeries[orderElem].Item2);
						_orderSeries.Remove(orderElem);
						return;
					}

					throw new ArgumentException("Неизвестный элемент графика {0}.".Put(elem));
				}
				finally
				{
					if (!_candlesSeries.Any() && !_indicatorSeries.Any() && !_tradeSeries.Any())
						NativeArea.AxisX.Interval = 1;

					_elements.Remove(elem);
				}
			}

			private IEnumerable<decimal> DrawIndicatorValues(ChartIndicatorElement elem, bool isFormed, double x, IDictionary<IIndicator, IIndicatorValue> innerValues)
			{
				var newYValues = new List<decimal>();

				var series = _indicatorSeries[elem].Item1;

				foreach (var pair in innerValues)
				{
					var complex = pair.Value as ComplexIndicatorValue;

					if (complex != null)
						newYValues.AddRange(DrawIndicatorValues(elem, isFormed, x, ((ComplexIndicatorValue)pair.Value).InnerValues));
					else
					{
						var y = pair.Value.GetValue<decimal>();

						var point = new DataPoint
						{
							XValue = x,
							YValues = new[] { (double)y },
							IsEmpty = !isFormed, // по корневому индикатору
							Color = elem.Color.ToWin()
						};

						series[pair.Key].Points.Add(point);

						if (!point.IsEmpty)
							newYValues.Add(y);
					}
				}

				return newYValues;
			}

			public void ProcessValues(IDictionary<DateTime, IDictionary<IChartElement, object>> values)
			{
				foreach (var elems in values)
				{
					var time = elems.Key;

					foreach (var pair in elems.Value)
					{
						var elem = pair.Key;

						if (!_elements.Contains(elem))
							continue;

						var chartTime = time.ToChartTime();

						var chartIndicator = elem as ChartIndicatorElement;

						//Trace.WriteLine("candle:" + candle.RelativeVolume);
						if (chartIndicator != null)
						{
							var dict = _indicatorSeries[chartIndicator].Item1;
							var firstSeries = dict.First().Value;

							DataPoint fakePoint = null;

							if (firstSeries.Points.Count > 0)
								fakePoint = firstSeries.Points.FindByValue(chartTime, "X", 0);

							if (fakePoint != null)
							{
								foreach (var p in dict)
								{
									p.Value.Points.RemoveWhere(point => point.XValue == chartTime);
								}
							}

							if (pair.Value != null)
							{
								var indValue = (ChartIndicatorValue)pair.Value;
								
								var complex = indValue.Value as ComplexIndicatorValue;

								var indDict = complex == null
									? new Dictionary<IIndicator, IIndicatorValue> { { chartIndicator.Indicator, indValue.Value } }
									: complex.InnerValues;

								var indValues = chartIndicator.IndicatorPainter != null
									? chartIndicator.IndicatorPainter.ProcessValues(time, indDict)
									: DrawIndicatorValues(chartIndicator, indValue.IsFormed, chartTime, indDict);

								foreach (var value in indValues)
								{
									UpdateMinMax(value);
								}
							}
							else
							{
								foreach (var p in dict)
								{
									p.Value.Points.Add(new DataPoint
									{
										XValue = chartTime,
										YValues = new[] { double.NaN },
										IsEmpty = true
									});
								}
							}

							continue;
						}

						var chartCandles = elem as ChartCandleElement;

						if (chartCandles != null)
						{
							var candle = (Candle)pair.Value;
							var tuple = _candlesSeries[chartCandles];

							if (candle == null)
							{
								var point = new DataPoint(chartTime, new double[4])
								{
									IsEmpty = true
								};

								tuple.First.Points.Add(point);
							}
							else
							{
								if (tuple.Second == null || tuple.Second.XValue != chartTime)
								{
									var point = new DataPoint(chartTime, new[]
									{
										(double)candle.LowPrice,
										(double)candle.HighPrice,
										(double)candle.OpenPrice,
										(double)candle.ClosePrice
									})
									{
										BorderColor = WinColor.DarkSlateGray,
										ToolTip = "{0}{6}O = {1}{6}H = {2}{6}L = {3}{6}C = {4}{6}V = {5}"
											.Put(candle.OpenTime, candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice, candle.TotalVolume, Environment.NewLine),
										Color = chartCandles.ColorPriceUp.ToWin(),
										BackSecondaryColor = chartCandles.ColorPriceDown.ToWin(),
									};

									tuple.First.Points.Add(point);

									tuple.Second = point;
								}
								else
								{
									tuple.Second.SetValueY((double)candle.LowPrice, (double)candle.HighPrice, (double)candle.OpenPrice, (double)candle.ClosePrice);
								}

								UpdateMinMax(candle.LowPrice);
								UpdateMinMax(candle.HighPrice);
							}

							continue;
						}

						var tradesElem = elem as ChartTradeElement;

						if (tradesElem != null)
						{
							var trade = (MyTrade)pair.Value;

							var tuple = _tradeSeries[tradesElem];

							if (trade == null)
							{
								tuple.Item1.Points.Add(new DataPoint
								{
									XValue = chartTime,
									YValues = new double[1],
									IsEmpty = true,
								});

								tuple.Item2.Points.Add(new DataPoint
								{
									XValue = chartTime,
									YValues = new double[1],
									IsEmpty = true,
								});
							}
							else
							{
								var point = new DataPoint
								{
									XValue = chartTime,
									YValues = new[] { (double)trade.Trade.Price },
									ToolTip = trade.ToString(),
								};

								var main = trade.Order.Direction == OrderDirections.Buy ? tuple.Item1 : tuple.Item2;
								var oppos = trade.Order.Direction == OrderDirections.Buy ? tuple.Item2 : tuple.Item1;

								main.Points.Add(point);
								oppos.Points.Add(new DataPoint
								{
									XValue = chartTime,
									YValues = new double[1],
									IsEmpty = true,
								});

								UpdateMinMax(trade.Trade.Price);
							}

							continue;
						}

						var ordersElem = elem as ChartOrderElement;

						if (ordersElem != null)
						{
							var order = (Order)pair.Value;

							var tuple = _orderSeries[ordersElem];

							if (order == null)
							{
								tuple.Item1.Points.Add(new DataPoint
								{
									XValue = chartTime,
									YValues = new double[1],
									IsEmpty = true,
								});

								tuple.Item2.Points.Add(new DataPoint
								{
									XValue = chartTime,
									YValues = new double[1],
									IsEmpty = true,
								});
							}
							else
							{
								var point = new DataPoint
								{
									XValue = chartTime,
									YValues = new[] { (double)order.Price },
									ToolTip = order.ToString(),
								};

								var main = order.Direction == OrderDirections.Buy ? tuple.Item1 : tuple.Item2;
								var oppos = order.Direction == OrderDirections.Buy ? tuple.Item2 : tuple.Item1;

								main.Points.Add(point);
								oppos.Points.Add(new DataPoint
								{
									XValue = chartTime,
									YValues = new double[1],
									IsEmpty = true,
								});

								UpdateMinMax(order.Price);
							}

							continue;
						}

						throw new ArgumentException("Неизвестный элемент графика {0}.".Put(elem));
					}
				}

				UpdateYMinMax();

				if (_chart.IsAutoScroll)
					NativeArea.AxisX.ScaleView.Scroll(ScrollType.Last);
			}

			public void UpdateYMinMax()
			{
				double? maxY;
				double? minY;

				if (NativeArea.AxisX.ScaleView.IsZoomed && _chart.MsChart.Series.Count > 0)
				{
					maxY = null;
					minY = null;

					var maxXView = NativeArea.AxisX.ScaleView.ViewMaximum;
					var minXView = NativeArea.AxisX.ScaleView.ViewMinimum;

					foreach (var serie in _chart.MsChart.GetSeries(NativeArea))
					{
						// смотрю видимые точки
						var points = (
							_chart.IsIndexed
								? serie.Points.Skip((int)minXView).Take((int)(maxXView - minXView))
								: serie.Points.Where(p => p.XValue >= minXView && p.XValue <= maxXView)
							).Where(p => !p.IsEmpty).ToList();

						if (points.IsEmpty())
							continue;

						var seriesMax = points.Max(p => p.YValues.Max());
						var seriesMin = points.Min(p => p.YValues.Min());

						if (maxY == null)
						{
							maxY = seriesMax;
							minY = seriesMin;
						}
						else
						{
							maxY = Math.Max(maxY.Value, seriesMax);
							minY = Math.Min(minY.Value, seriesMin);
						}
					}
				}
				else
				{
					minY = _minY;
					maxY = _maxY;
				}

				UpdateAxisMinMax(minY ?? 0, maxY ?? 1);

				if (_area.AxisValuesPosition)
				{
					NativeArea.AxisY2.Maximum = NativeArea.AxisY.Maximum;
					NativeArea.AxisY2.Minimum = NativeArea.AxisY.Minimum;
					NativeArea.AxisY2.Interval = NativeArea.AxisY.Interval;
				}
			}

			private void UpdateMinMax(decimal value)
			{
				var dValue = (double)value;

				_minY = _minY == null ? dValue : _minY.Value.Min(dValue);
				_maxY = _maxY == null ? dValue : _maxY.Value.Max(dValue);
			}

			private static int RoundOff(int number, int interval, bool roundUp)
			{
				var remainder = Math.Abs(number % interval);

				if (roundUp)
					number += interval - remainder;
				else
					number -= remainder;

				return number;
			}

			private void UpdateAxisMinMax(double min, double max)
			{
				var axis = NativeArea.AxisY;

				var prevMax = axis.Maximum;
				var prevMin = axis.Minimum;

				axis.Maximum = max.Ceiling();
				axis.Minimum = min.Floor();

				// Перерасчет макс и мин значений пропорционально высоте плот областей панелей
				var plotAreaHeight = (_chart.MsChart.Size.Height * (NativeArea.Position.Height / 100) * (NativeArea.InnerPlotPosition.Height) / 100);
				if (plotAreaHeight != 0)
				{
					// значения кратности по умолчанию
					int[] multList = { 1, 5, 10, 20, 50, 100, 200, 500, 1000, 5000, 10000 };

					double yValuesNumber = plotAreaHeight / 10; // 10 - минимально допустимое расстояние 
					// между значениями по ординате (зависит от размера фонта)

					var multiplicity = (axis.Maximum - axis.Minimum) / yValuesNumber;

					var interval = 1;
					foreach (var mult in multList)
					{
						var m = (multiplicity / mult).To<int>();
						if (m != 0) continue;
						interval = mult;
						break;
					}

					multiplicity = RoundOff((int)multiplicity, interval, true);

					axis.Interval = multiplicity;

					axis.Maximum = RoundOff((int)axis.Maximum, (int)multiplicity, true);
					axis.Minimum = RoundOff((int)axis.Minimum, (int)multiplicity, false);
				}

				// NOTE: MIKA
				// http://social.msdn.microsoft.com/Forums/eu/MSWinWebChart/thread/81c19741-26c0-4a28-9507-bb0b829afb14
				if (axis.Maximum == axis.Minimum)
				{
					axis.Maximum = axis.Minimum + 10;
					NativeArea.RecalculateAxesScale();
					return;
				}

				if (axis.Maximum < axis.Minimum)
					throw new InvalidOperationException("Для значений min={0} max={1} неправильно были расчитаны экстремумы {2} {3}.".Put(min, max, axis.Minimum, axis.Maximum));

				if (prevMin != axis.Minimum || prevMax != axis.Maximum)
					NativeArea.RecalculateAxesScale();
			}

			public void Reset(IEnumerable<IChartElement> elements)
			{
				foreach (var element in elements)
				{
					if (!_elements.Contains(element))
						continue;

					var indicator = element as ChartIndicatorElement;

					if (indicator != null)
					{
						_indicatorSeries[indicator].Item1.Values.ForEach(_chart.MsChart.ClearPoints);
					}

					var candles = element as ChartCandleElement;

					if (candles != null)
					{
						var tuple = _candlesSeries[candles];

						_chart.MsChart.ClearPoints(tuple.First);
						tuple.Second = null;
					}

					var trades = element as ChartTradeElement;

					if (trades != null)
					{
						var tuple = _tradeSeries[trades];

						_chart.MsChart.ClearPoints(tuple.Item1);
						_chart.MsChart.ClearPoints(tuple.Item2);
					}

					var orders = element as ChartOrderElement;

					if (orders != null)
					{
						var tuple = _orderSeries[orders];

						_chart.MsChart.ClearPoints(tuple.Item1);
						_chart.MsChart.ClearPoints(tuple.Item2);
					}
				}

				_minY = null;
				_maxY = null;
			}
		}

		private sealed class ChartAreaCollection : BaseList<ChartArea>, IPersistable
		{
			private readonly Chart _chart;
			private readonly Dictionary<ChartArea, AreaInfo> _info = new Dictionary<ChartArea, AreaInfo>();
			private readonly WinColor _lineColor = WinColor.FromArgb(64, 64, 64, 64);

			public ChartAreaCollection(Chart chart)
			{
				if (chart == null)
					throw new ArgumentNullException("chart");

				_chart = chart;
			}

			protected override void OnAdded(ChartArea item)
			{
				base.OnAdded(item);

				var area = new WinChartArea("ChartArea".SeriesName())
				{
					AxisX =
					{
						IsLabelAutoFit = false,
						Interval = 1,
						ScrollBar =
						{
							Size = 20D,
							Enabled = false,
							ButtonStyle = ScrollBarButtonStyles.All,
							IsPositionedInside = false
						},
						ScaleView =
						{
							Zoomable = true,
							SizeType = DateTimeIntervalType.Auto,
							SmallScrollSizeType = DateTimeIntervalType.Minutes,
							SmallScrollSize = 1
						},
						LabelStyle =
						{
							Font = new Font("Trebuchet MS", 8.25F, System.Drawing.FontStyle.Bold),
							IsEndLabelVisible = false, // временный фикс необходимый для правильного алаймента панелей.
							Format = "yyyy.MM.dd HH:mm",
						},
						LineColor = _lineColor,
						MajorGrid =
						{
							LineColor = _lineColor,
							LineWidth = 1,
							IntervalType = DateTimeIntervalType.Days,
							Interval = 1,
							Enabled = true
						},
						MinorGrid =
						{
							LineColor = _lineColor,
							IntervalType = DateTimeIntervalType.Hours,
							Interval = 1
						},
						IntervalAutoMode = IntervalAutoMode.VariableCount,
						Enabled = AxisEnabled.False
					},
					AxisY =
					{
						//Interval = 5,
						IntervalType = DateTimeIntervalType.NotSet,
						IsLabelAutoFit = false,
						LabelStyle =
						{
							Font = new Font("Trebuchet MS", 8.25F, System.Drawing.FontStyle.Bold),
							Format = "{0:#,##}",
							//IntervalType = DateTimeIntervalType.Number
						},
						LineColor = _lineColor,
						MajorGrid =
						{
							LineColor = _lineColor,
							LineWidth = 1,
							IntervalType = DateTimeIntervalType.Number,
							Enabled = true
						},
						MinorGrid =
						{
							LineColor = _lineColor,
							IntervalType = DateTimeIntervalType.Number,
							Enabled = false
						},
						IsStartedFromZero = false,
						Enabled = AxisEnabled.True,
					},
					AxisY2 =
					{
						//Interval = 5,
						IntervalType = DateTimeIntervalType.NotSet,
						IsLabelAutoFit = false,
						LabelStyle =
						{
							Font = new Font("Trebuchet MS", 8.25F, System.Drawing.FontStyle.Bold),
							Format = "{0:#,##}",
							//IntervalType = DateTimeIntervalType.Number
						},
						LineColor = _lineColor,
						MajorGrid =
						{
							LineColor = _lineColor,
							LineWidth = 1,
							IntervalType = DateTimeIntervalType.Number,
							Enabled = true
						},
						MinorGrid =
						{
							LineColor = _lineColor,
							IntervalType = DateTimeIntervalType.Number,
							Enabled = false
						},
						IsStartedFromZero = false,
					},
					BackColor = WinColor.FromArgb(64, 165, 191, 228),
					BackSecondaryColor = WinColor.White,
					BackGradientStyle = GradientStyle.TopBottom,
					BorderColor = _lineColor,
					BorderDashStyle = ChartDashStyle.Solid,
					ShadowColor = WinColor.Transparent,
					CursorX =
					{
						AutoScroll = true,
						IsUserEnabled = true,
						IsUserSelectionEnabled = true,
						LineColor = WinColor.LightBlue,
						SelectionColor = WinColor.Gainsboro,
						Interval = 0
					},
					Position =
					{
						Auto = false,
						//Height = item.Height,
						Width = 100f,
						X = 0f
						//Y = 70f
					}
				};

				((INotifyPropertyChanged)item).PropertyChanged += delegate
				{
					UpdateAreasPosition();
					UpdateAreasAxisPosition();
				};

				_info.Add(item, new AreaInfo(item, area, _chart));

				_chart.MsChart.ChartAreas.Add(area);

				UpdateAreasPosition();
				UpdateAreasAxisPosition();
				AlignAreas();
			}

			protected override void OnRemoved(ChartArea item)
			{
				base.OnRemoved(item);

				RemoveArea(item);

				UpdateAreasPosition();
				AlignAreas();
			}

			private void RemoveArea(ChartArea item)
			{
				_chart.MsChart.ChartAreas.Remove(_info[item].NativeArea);
				_info.Remove(item);
			}

			protected override bool OnClearing()
			{
				foreach (var area in this)
					RemoveArea(area);

				return base.OnClearing();
			}

			private void UpdateAreasAxisPosition()
			{
				foreach (var pair in _info)
				{
					var area = pair.Key;
					var nativeArea = pair.Value.NativeArea;

					nativeArea.AxisY2.Enabled = area.AxisValuesPosition ? AxisEnabled.True : AxisEnabled.False;
					nativeArea.AxisY.Enabled = area.AxisValuesPosition ? AxisEnabled.False : AxisEnabled.True;
				}
			}

			private void UpdateAreasPosition()
			{
				var totalHeight = this.Sum(area => area.Height) + (Count - 1).Max(0) * _chart.MsChart.ToRelativeY(MsChartEx.AreaMargin);
				var y = 0f;

				var index = 0;

				foreach (var pair in _info)
				{
					var area = pair.Key;
					var nativeArea = pair.Value.NativeArea;

					nativeArea.Position.Height = (area.Height * 100 / totalHeight).Min(100);
					nativeArea.Position.Y = y;

					if (index < (Count - 1))
						nativeArea.Position.Y += _chart.MsChart.ToRelativeY(MsChartEx.AreaMargin);

					y = nativeArea.Position.Bottom;

					index++;
				}
			}

			private void AlignAreas()
			{
				if (Count == 0)
					return;

				var index = 0;

				foreach (var pair in _info)
				{
					var area = pair.Key;
					var nativeArea = pair.Value.NativeArea;

					if (area.IsAligned && index > 0)
					{
						nativeArea.AlignWithChartArea = _info.First().Value.NativeArea.Name;
						nativeArea.AlignmentStyle = AreaAlignmentStyles.All;
						nativeArea.AlignmentOrientation = AreaAlignmentOrientations.Vertical;
					}

					if (!area.IsAligned || index == (Count - 1))
					{
						nativeArea.AxisX.Enabled = AxisEnabled.True;
						nativeArea.AxisX.ScrollBar.Enabled = true;
						nativeArea.AxisX.LabelStyle.Enabled = true;
						nativeArea.AxisX.MajorTickMark.Enabled = true;
					}
					else
					{
						// эквивалент area.Area.AxisX.Enabled = AxisEnabled.False;
						// оставляет AxisX включенным для ресайза панелей
						nativeArea.AxisX.ScrollBar.Enabled = false;
						nativeArea.AxisX.LabelStyle.Enabled = false;
						nativeArea.AxisX.MajorTickMark.Enabled = false;
					}

					index++;
				}
			}

			public void UpdateYMinMax()
			{
				foreach (var pair in _info)
					pair.Value.UpdateYMinMax();
			}

			private void ProcessDraw(Action action)
			{
				if (action == null)
					throw new ArgumentNullException("action");

				_chart.MsChart.Series.SuspendUpdates();

				try
				{
					action();
				}
				finally
				{
					_chart.MsChart.Series.ResumeUpdates();
					_chart.MsChart.Series.Invalidate();
				}
			}

			public void ProcessValues(IDictionary<DateTime, IDictionary<IChartElement, object>> values)
			{
				if (values == null)
					throw new ArgumentNullException("values");

				ProcessDraw(() =>
				{
					foreach (var pair in _info)
						pair.Value.ProcessValues(values);
				});
			}

			public void Reset(IEnumerable<IChartElement> elements)
			{
				ProcessDraw(() =>
				{
					foreach (var info in _info.Values)
						info.Reset(elements);
				});
			}

			public void Load(SettingsStorage storage)
			{
				this.AddRange(storage.GetValue<IEnumerable<SettingsStorage>>("Areas").Select(s =>
				{
					var area = new ChartArea();
					area.Load(s);
					return area;
				}));
			}

			public void Save(SettingsStorage storage)
			{
				storage.SetValue("Areas", this.Select(a =>
				{
					var s = a.Save();
					s.SetValue("Height", _info[a].NativeArea.Position.Height);
					return s;
				}).ToArray());
			}
		}

		/// <summary>
		/// Создать <see cref="Chart"/>.
		/// </summary>
		public Chart()
		{
			InitializeComponent();

			var chart = MsChart;
			//chart.AreaSizeChanged += () => Changed.SafeInvoke();
			chart.Palette = ChartColorPalette.SemiTransparent;

			_areas = new ChartAreaCollection(this);

			IsIndexed = false;

			BackColor = Colors.LightSteelBlue;
			BackSecondaryColor = Colors.White;
			BackGradientStyle = GradientStyle.TopBottom;

			MsChart.AxisViewChanged += OnChartAxisViewChanged;
			MsChart.AxisScrollBarClicked += OnAxisScrollBarClicked;
			MsChart.SizeChanged += OnChartSizeChanged;
			MsChart.AreaSizeChanged += OnChartAreaSizeChanged;

			IsAutoScroll = true;
		}

		/// <summary>
		/// Индексированы ли данные. По-умолчанию включено.
		/// </summary>
		public bool IsIndexed { get; set; }

		private readonly ChartAreaCollection _areas;

		/// <summary>
		/// Области графика.
		/// </summary>
		public INotifyList<ChartArea> Areas
		{
			get { return _areas; }
		}

		/// <summary>
		/// Фон графика для начала градиента.
		/// </summary>
		[DisplayName("Цвет 1")]
		[Description("Фон графика для начала градиента.")]
		[Category("Фон")]
		public WpfColor BackColor
		{
			get { return MsChart.BackColor.ToWpf(); }
			set { MsChart.BackColor = value.ToWin(); }
		}

		/// <summary>
		/// Фон графика для окончания градиента.
		/// </summary>
		[DisplayName("Цвет 2")]
		[Description("Фон графика для окончания градиента.")]
		[Category("Фон")]
		public WpfColor BackSecondaryColor
		{
			get { return MsChart.BackSecondaryColor.ToWpf(); }
			set { MsChart.BackSecondaryColor = value.ToWin(); }
		}

		/// <summary>
		/// Стиль градиента для фона.
		/// </summary>
		[DisplayName("Градиент")]
		[Description("Стиль градиента для фона.")]
		[Category("Фон")]
		public GradientStyle BackGradientStyle
		{
			get { return MsChart.BackGradientStyle; }
			set { MsChart.BackGradientStyle = value; }
		}

		/// <summary>
		/// Скроллировать автоматически области <see cref="Areas"/> при появлении новых данных. По-умолчанию включено.
		/// </summary>
		[Browsable(false)]
		public bool IsAutoScroll { get; set; }

		/// <summary>
		/// Загрузить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public void Load(SettingsStorage storage)
		{
			BackColor = storage.GetValue<SettingsStorage>("BackColor").ToColor();
			BackSecondaryColor = storage.GetValue<SettingsStorage>("BackSecondaryColor").ToColor();
			BackGradientStyle = storage.GetValue<string>("BackGradientStyle").To<GradientStyle>();
			IsAutoScroll = storage.GetValue("IsAutoScroll", true);
			_areas.Load(storage.GetValue<SettingsStorage>("Areas"));
		}

		/// <summary>
		/// Сохранить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public void Save(SettingsStorage storage)
		{
			storage.SetValue("BackColor", BackColor.ToStorage());
			storage.SetValue("BackSecondaryColor", BackSecondaryColor.ToStorage());
			storage.SetValue("BackGradientStyle", BackGradientStyle.ToString());
			storage.SetValue("IsAutoScroll", IsAutoScroll);
			storage.SetValue("Areas", _areas.Save());
		}

		private void OnChartAreaSizeChanged()
		{
			_areas.UpdateYMinMax();
		}

		private void OnChartSizeChanged(object sender, EventArgs e)
		{
			_areas.UpdateYMinMax();
		}

		// обработчик зумминга
		private void OnAxisScrollBarClicked(object sender, ScrollBarEventArgs e)
		{
			_areas.UpdateYMinMax();
		}

		private void OnChartAxisViewChanged(object sender, ViewEventArgs e)
		{
			_areas.UpdateYMinMax();
		}

	    /// <summary>
		/// Сбросить отрисованные ранее значения элементов графика.
		/// </summary>
		/// <param name="elements">Элементы графика.</param>
		public void Reset(IEnumerable<IChartElement> elements)
		{
			if (elements == null)
				throw new ArgumentNullException("elements");

			_areas.Reset(elements);
		}

		/// <summary>
		/// Обработать новые данные.
		/// </summary>
		/// <param name="values">Новые данные.</param>
		public void ProcessValues(IDictionary<DateTime, IDictionary<IChartElement, object>> values)
		{
			_areas.ProcessValues(values);
		}
	}
}
